To begin examining the process of implementing existing .NET interfaces, let’s first look at the role of IEnumerable and IEnumerator. Recall that C# supports a keyword named foreach that allows you to iterate over the contents of any array type:
// Iterate over an array of items. int[] myArrayOfInts = {10, 20, 30, 40}; foreach(int i in myArrayOfInts) { Console.WriteLine(i); }
While it may seem that only array types can make use of this construct, the truth of the matter is any type supporting a method named GetEnumerator() can be evaluated by the foreach construct. To illustrate, begin by creating a new Console Application project named CustomEnumerator. Next, add the Car.cs and Radio.cs files defined in the SimpleException example of Chapter 7 (via the Project ? Add Existing Item menu option).
Note You may wish to rename the namespace containing the Car and Radio types to CustomEnumerator, to avoid having to import the CustomException namespace within this new project.
Now, insert a new class named Garage that stores a set of Car objects within a System.Array:
// Garage contains a set of Car objects. public class Garage { private Car[] carArray = new Car[4]; // Fill with some Car objects upon startup. public Garage() { carArray[0] = new Car("Rusty", 30); carArray[1] = new Car("Clunker", 55); carArray[2] = new Car("Zippy", 30); carArray[3] = new Car("Fred", 30); } }
Ideally, it would be convenient to iterate over the Garage object’s subitems using the foreach construct, just like an array of data values:
// This seems reasonable... public class Program { static void Main(string[] args) { Console.WriteLine("***** Fun with IEnumerable / IEnumerator *****\n"); Garage carLot = new Garage(); // Hand over each car in the collection? foreach (Car c in carLot) { Console.WriteLine("{0} is going {1} MPH", c.PetName, c.CurrentSpeed); } Console.ReadLine(); } }
Sadly, the compiler informs you that the Garage class does not implement a method named GetEnumerator(). This method is formalized by the IEnumerable interface, which is found lurking within the System.Collections namespace.
Notes In the next chapter you will learn about the role of generics and the System.Collections.Generic namespace. As you will see, this namespace contain generic versions of IEnumerable/IEnumerator that provide a more type-safe way to iterate over subobjects.
Classes or structures that support this behavior advertise that they are able to expose contained subitems to the caller (in this example, the foreach keyword itself):
// This interface informs the caller // that the object's subitems can be enumerated. public interface IEnumerable { IEnumerator GetEnumerator(); }
As you can see, the GetEnumerator() method returns a reference to yet another interface named System.Collections.IEnumerator. This interface provides the infrastructure to allow the caller to traverse the internal objects contained by the IEnumerable-compatible container:
// This interface allows the caller to // obtain a container's subitems. public interface IEnumerator { bool MoveNext (); // Advance the internal position of the cursor. object Current { get;} // Get the current item (read-only property). void Reset (); // Reset the cursor before the first member. }
If you wish to update the Garage type to support these interfaces, you could take the long road and implement each method manually. While you are certainly free to provide customized versions of GetEnumerator(), MoveNext(), Current, and Reset(), there is a simpler way. As the System.Array type (as well as many other collection classes) already implements IEnumerable and IEnumerator, you can simply delegate the request to the System.Array as follows:
using System.Collections; ... public class Garage : IEnumerable { // System.Array already implements IEnumerator! private Car[] carArray = new Car[4]; public Garage() { carArray[0] = new Car("FeeFee", 200, 0); carArray[1] = new Car("Clunker", 90, 0); carArray[2] = new Car("Zippy", 30, 0); carArray[3] = new Car("Fred", 30, 0); } public IEnumerator GetEnumerator() { // Return the array object's IEnumerator. return carArray.GetEnumerator(); } }
Once you have updated your Garage type, you can now safely use the type within the C# foreach construct. Furthermore, given that the GetEnumerator() method has been defined publicly, the object user could also interact with the IEnumerator type:
// Manually work with IEnumerator. IEnumerator i = carLot.GetEnumerator(); i.MoveNext(); Car myCar = (Car)i.Current; Console.WriteLine("{0} is going {1} MPH", myCar.PetName, myCar.CurrentSpeed);
However, if you prefer to hide the functionality of IEnumerable from the object level, simply make use of explicit interface implementation:
IEnumerator IEnumerable.GetEnumerator() { // Return the array object's IEnumerator. return carArray.GetEnumerator(); }
By doing so, the casual object user will not find the Garage’s GetEnumerator() method, while the foreach construct will obtain the interface in the background when necessary.
Source Code The CustomEnumerator project is located under the Chapter 9 subdirectory.
Historically, when you wanted to build a custom collection (such as Garage) that supported foreach enumeration, implementing the IEnumerable interface (and possibly the IEnumerator interface) was your only option. However, there’s an alternative way to build types that work with the foreach loop via iterators.
Simply put, an iterator is a member that specifies how a container’s internal items should be returned when processed by foreach. While the iterator method must still be named GetEnumerator(), and the return value must still be of type IEnumerator, your custom class does not need to implement any of the expected interfaces.
To illustrate, create a new Console Application project named CustomEnumeratorWithYield and insert the Car, Radio, and Garage types from the previous example (again, renaming your namespace definitions to the current project if you like). Now, retrofit the current Garage type as follows:
public class Garage { private Car[] carArray = new Car[4]; ... // Iterator method. public IEnumerator GetEnumerator() { foreach (Car c in carArray) { yield return c; } } }
Notice that this implementation of GetEnumerator() iterates over the subitems using internal foreach logic and returns each Car to the caller using the yield return syntax. The yield keyword is used to specify the value (or values) to be returned to the caller’s foreach construct. When the yield return statement is reached, the current location in the container is stored, and execution is restarted from this location the next time the iterator is called (you'll see the details behind this “magic” in just a moment).
Iterator methods are not required to make use of the foreach keyword to return its contents. It is also permissible to define this iterator method as follows:
public IEnumerator GetEnumerator() { yield return carArray[0]; yield return carArray[1]; yield return carArray[2]; yield return carArray[3]; }
In this implementation, notice that the GetEnumerator() method is explicitly returning a new value to the caller with each pass through. Doing so for this example makes little sense, given that if we were to add more objects to the carArray member variable, our GetEnumerator() method would now be out of sync. Nevertheless, this syntax can be useful when you wish to return local data from a method for processing by the foreach syntax.
It is also interesting to note that the yield keyword can technically be used within any method, regardless of its name. These methods (which are technically called named iterators) are also unique in that they can take any number of arguments. When building a named iterator, be very aware that the method will return the IEnumerable interface, rather than the expected IEnumerator-compatible type. To illustrate, you could add the following method to the Garage type:
public IEnumerable GetTheCars(bool ReturnRevesed) { // Return the items in reverse. if (ReturnRevesed) { for (int i = carArray.Length; i != 0; i--) { yield return carArray[i-1]; } } else { // Return the items as placed in the array. foreach (Car c in carArray) { yield return c; } } }
Notice that the new method allows the caller to obtain the subitems in sequential order, as well as in reverse order, if the incoming parameter has the value true. You could now interact with our new method as follows:
static void Main(string[] args) { Console.WriteLine("***** Fun with the Yield Keyword *****\n"); Garage carLot = new Garage(); // Get items using GetEnumerator(). foreach (Car c in carLot) { Console.WriteLine("{0} is going {1} MPH", c.PetName, c.CurrentSpeed); } Console.WriteLine(); // Get items (in reverse!) using named iterator. foreach (Car c in carLot.GetTheCars(true)) { Console.WriteLine("{0} is going {1} MPH", c.PetName, c.CurrentSpeed); } Console.ReadLine(); }
As you may agree, named iterators are helpful constructs, in that a single custom container can define multiple ways to request the returned set.
When the C# compiler encounters an iterator method, it dynamically generates a nested class definition within the scope of the defining type (Garage in this case). The autogenerated nested class implements the GetEnumerator(), MoveNext(), and Current members on your behalf (oddly, the Reset() method is not implemented, and you will receive a runtime exception if you attempt to call it). If you load the current application into ildasm.exe, you’ll find two nested types, each of which accounts for the logic required by a specific iterator method. Notice in Figure 9-8 that these compiler-generated types have been named <GetEnumerator>d__0 and <GetTheCars>d__6.
Figure 9-8. Iterator methods are internally implemented with the help of an autogenerated nested class
If you use ildasm.exe to view the implementation of the GetEnumerator() method of the Garage type, you’d find that it has been implemented to make use of the <GetEnumerator>d__0 type behind the scenes (the nested <GetTheCars>d__6 type is used by the GetTheCars() method in a similar manner).
.method public hidebysig instance class [mscorlib]System.Collections.IEnumerator GetEnumerator() cil managed { ... newobj instance void CustomEnumeratorWithYield.Garage/'<GetEnumerator>>d__0'::.ctor(int32) ... } // end of method Garage::GetEnumerator
So, to wrap up our look at building enumerable objects, remember that for your custom types to work with the C# foreach keyword, the container must define a method named GetEnumerator(), which has been formalized by the IEnumerable interface type. The implementation of this method is typically achieved by simply delegating it to the internal member that is holding onto the subobjects; however, it is also possible to make use of the yield return syntax to provide multiple “named iterator” methods.
Source Code The CustomEnumeratorWithYield project is located under the Chapter 9 subdirectory.